package cache_local

import (
	"gitee.com/kingzyt/common/concurrent"
	"gitee.com/kingzyt/common/log"
	"gitee.com/kingzyt/common/roughTimeNow"
	. "gitee.com/kingzyt/common/util"
	"time"
)

var ROUGH_NOW = roughTimeNow.NewRoughTimeNow(time.Second * 10)

type CacheUnit struct {
	data      interface{}
	expiredTS int64
}

type MemCache struct {
	name string

	datas           *concurrent.ConcurrentMap
	ignoreGetLog    bool
	keyExpiredTimeS int32
	minCleanT       int64
}

var g_clean_list []*MemCache
var g_procsAfterCacheDailyCleaning []func()

func AddProcAfterCacheDailyClean(proc func()) {
	if proc == nil {
		panic("proc is nil")
	}
	g_procsAfterCacheDailyCleaning = append(g_procsAfterCacheDailyCleaning, proc)
}

func init() {
	lastHour := time.Now().Hour()
	//当前采用3点清空缓存的方法, 来简单粗暴地解决缓存的内存消耗累积的问题
	go UpdateProcWithNow(time.Minute*10, false, func(now time.Time) (ok bool) {
		curHour := now.Hour()
		curT := now.UnixNano()
		if curHour == New_day_checkpoint_hour && lastHour != curHour {
			clearedDatasCount := int64(0)
			clearedCacheCount := 0
			for _, mc := range g_clean_list {
				if mc.minCleanT < curT {
					clearedDatasCount += int64(mc.datas.Size())
					mc.datas.Clear()
					clearedCacheCount++
				}
			}
			log.Info("MemCache", log.I, "clear %d/%d caches, total cleared datas count:%d", clearedCacheCount, len(g_clean_list), clearedDatasCount)
		}

		for _, proc := range g_procsAfterCacheDailyCleaning {
			func() {
				defer DeferRecoverPanic("DoProcAfterCacheDayCleaning")
				proc()
			}()
		}

		lastHour = curHour
		return true
	})
}

func NewMemCacheWithKeyExpiredTimeS(name string, keyExpiredTimeS int32, ignoreGetLog bool) ICache {
	rlt := &MemCache{
		name:            name,
		datas:           concurrent.NewConcurrentMap(),
		ignoreGetLog:    ignoreGetLog,
		keyExpiredTimeS: default_key_expired_time_s,
	}

	if keyExpiredTimeS > 0 {
		rlt.keyExpiredTimeS = keyExpiredTimeS
	}
	rlt.minCleanT = ROUGH_NOW.Now().UnixNano() + int64(rlt.keyExpiredTimeS)*int64(time.Second)

	g_clean_list = append(g_clean_list, rlt)
	//rlt.bgCheckRemoveExpiredKey()

	return rlt
}

func (self *MemCache) Name() string {
	return self.name
}

func (self *MemCache) getCore(needCacheRepeatedMiss bool, key interface{}, args ...interface{}) (value interface{}, ok bool) {
	_, _ = self.datas.Update(key, func(v interface{}) (newV interface{}) {
		if v != nil {
			u := v.(*CacheUnit)

			if u.expiredTS <= 0 || u.expiredTS >= ROUGH_NOW.NowS() {
				if !self.ignoreGetLog {
					if u.data != nil {
						log.Info(self.name, log.Trace, "get ok [%v]", key)
					} else {
						log.Error(self.name, "cache repeated miss: key %v", key)
					}
				}

				newV = v

				value = u.data
				ok = true
				return v
			} else {
				log.Info(self.name, log.Trace, "expired [%v]", key)
			}
		}
		log.Info(self.name, log.Trace, "get miss [%v]", key)

		// add repeated attack protection
		if needCacheRepeatedMiss {
			newV = &CacheUnit{data: nil, expiredTS: ROUGH_NOW.NowS() + single_key_repeated_invalid_attack_protection_time_s}
		}
		return newV
	})

	if needCacheRepeatedMiss {
		if value == nil && ok {
			panic(ErrCacheRepeatedMiss)
		}
	}

	return
}

func (self *MemCache) GetInternal(key interface{}, args ...interface{}) (value interface{}, ok bool) {
	return self.getCore(false, key, args...)
}

func (self *MemCache) Get(key interface{}, args ...interface{}) (value interface{}, ok bool) {
	return self.getCore(true, key, args...)
}

func (self *MemCache) setCore(expiredTS int64, key interface{}, value interface{}) {
	log.Info(self.name, log.Trace, "set [%v]", key)

	self.datas.Put(key, &CacheUnit{data: value, expiredTS: expiredTS})
}

func (self *MemCache) SetWithTTL(ttlS int, key interface{}, args ...interface{}) {
	self.setCore(ROUGH_NOW.NowS()+int64(ttlS), key, args[0])
}
func (self *MemCache) SetWithExpiredT(expiredTS int64, key interface{}, args ...interface{}) {
	self.setCore(expiredTS, key, args[0])
}
func (self *MemCache) Set(key interface{}, args ...interface{}) {
	self.setCore(ROUGH_NOW.NowS()+int64(self.keyExpiredTimeS), key, args[0])
}
func (self *MemCache) SetDirty(key interface{}, args ...interface{}) {
	oldV, _ := self.datas.Remove(key)
	if oldV != nil {
		log.Info(self.name, log.Trace, "dirty [%v]", key)
		// do dirty when data change
		self.NotifyCacheChange(key, args...)
	}
}
func (self *MemCache) ForceAllClear() {
	self.datas.Clear()
}

func (self *MemCache) NotifyCacheChange(key interface{}, args ...interface{}) {
	// send {self.name, key, args...} to cache notify center, then push to other server
	//......
}
